package hu.bme.mit.inf.seample.gen;

import hu.bme.mit.inf.seample.gen.util.CodeGeneratorUtil;
import hu.bme.mit.inf.seample.gen.util.CopyRestJars;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.emf.mwe.core.WorkflowContextDefaultImpl;
import org.eclipse.emf.mwe.core.issues.IssuesImpl;
import org.eclipse.emf.mwe.core.monitor.ProgressMonitorAdapter;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.xpand2.Generator;
import org.eclipse.xpand2.output.JavaBeautifier;
import org.eclipse.xpand2.output.NoChangesVetoStrategy;
import org.eclipse.xpand2.output.Outlet;
import org.eclipse.xpand2.output.Output;
import org.eclipse.xpand2.output.OutputImpl;
import org.eclipse.xtend.typesystem.MetaModel;
import org.eclipse.xtend.typesystem.emf.EmfMetaModel;

import seample.SeamplePackage;

/**
 * Main generator in Seam Application Skeleton generation. Generates a simple
 * project and a contents from the source model.
 * 
 * @author Mark Czotter
 * 
 */
public class SeamApplicationSkeletonGenerator {

	private static final String FN_SRC = "ejbModule";
	private static final String SLOT = "seamsystem";
	private static final String ROOT_TEMPLATE = "hu::bme::mit::obp::seammodel::gen::Root::root";
	private static final String FILE_ENCODING = "Cp1250";

	private final SeamGenerationDescriptor descriptor;
	private Map<String, IFolder> packageNamesToPackageFolders;

	public SeamApplicationSkeletonGenerator(SeamGenerationDescriptor descriptor) {
		this.descriptor = descriptor;
	}

	/**
	 * Generates the application skeleton
	 * 
	 * @param monitor
	 * @throws CoreException
	 * @throws ProjectNameNotSettedException
	 */
	public void generate(IProgressMonitor monitor) throws CoreException,
			ProjectNameNotSettedException {
		if (monitor.isCanceled())
			throw new OperationCanceledException();
		// FIXME: uses static variable for storing the selected descriptor, so
		// the extension can access to it for the package names.
		CodeGeneratorUtil.setSelectedDescriptor(getDescriptor());
		// first generate the project, if the descriptor's project not exist
		if (descriptor.getProject() == null
				|| !descriptor.getProject().exists()) {
			generateProject(monitor);
		}
		if (monitor.isCanceled())
			throw new OperationCanceledException();
		try {
			new CopyRestJars().copyToLib(descriptor.getEarProjectName());
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (monitor.isCanceled())
			throw new OperationCanceledException();
		// create a source folder
		IFolder src = createFolder(descriptor.getProject(), FN_SRC,
				new SubProgressMonitor(monitor, 10));
		// generate the packages
		packageNamesToPackageFolders = createPackages(src,
				descriptor.getAllPackageNames(), monitor);
		// then generate all its contents based on the generator model
		if (monitor.isCanceled())
			throw new OperationCanceledException();
		invokeGenerationWorkflow(monitor);
		// refresh the project
		getDescriptor().getProject().refreshLocal(IResource.DEPTH_INFINITE,
				new SubProgressMonitor(monitor, 10));
	}

	/**
	 * Creates the given packageNames, in the given src folder.
	 * 
	 * @param project
	 * @param src
	 * @param allPackageNames
	 * @throws CoreException
	 */
	private Map<String, IFolder> createPackages(IFolder src,
			List<String> allPackageNames, IProgressMonitor monitor)
			throws CoreException {
		Map<String, IFolder> packages = new HashMap<String, IFolder>();
		for (String packageName : allPackageNames) {
			if (packageName == null || packageName.isEmpty())
				continue;
			if (packageName.contains(".")) {
				String[] packageFragments = packageName.split("[//.]");
				IFolder fragmentContainer = src;
				for (String fragment : packageFragments) {
					fragmentContainer = createFolder(fragmentContainer,
							fragment, new SubProgressMonitor(monitor, 5));
				}
				if (fragmentContainer != null) {
					packages.put(packageName, fragmentContainer);
				}
			} else {
				IFolder folder = createFolder(src, packageName,
						new SubProgressMonitor(monitor, 5));
				if (folder != null) {
					packages.put(packageName, folder);
				}
			}
		}
		return packages;
	}

	/**
	 * Invokes the generator workflow
	 * 
	 * @param monitor
	 */
	private void invokeGenerationWorkflow(IProgressMonitor monitor) {
		// create XPand generator
		Generator gen = new Generator();
		// set the MetaModels
		MetaModel seammm = new EmfMetaModel(SeamplePackage.eINSTANCE);
		MetaModel umlmm = new EmfMetaModel(UMLPackage.eINSTANCE);
		gen.addMetaModel(seammm);
		gen.addMetaModel(umlmm);
		// set the main template
		gen.setExpand(ROOT_TEMPLATE + " FOR " + SLOT);
		// set the output for generated files
		gen.setOutput(getDefaultOutput());
		gen.setPrSrcPaths(getDescriptor().getProject().getFolder(FN_SRC)
				.getLocation().toOSString());
		// set readed model instance into the slot
		WorkflowContextDefaultImpl ctx = new WorkflowContextDefaultImpl();
		ctx.set(SLOT, getDescriptor().getSeamSystem());
		// finally invoke the generator component
		gen.invoke(ctx, new ProgressMonitorAdapter(monitor), new IssuesImpl());
	}

	/**
	 * Creates a default output for the create projects SRC folder.
	 * 
	 * @return
	 */
	private Output getDefaultOutput() {
		Output output = new OutputImpl();
		for (Outlet o : createOutlets()) {
			output.addOutlet(o);
		}
		return output;
	}

	/**
	 * Creates Outlets from the packages.
	 * 
	 * @return
	 */
	private List<Outlet> createOutlets() {
		List<Outlet> outlets = new ArrayList<Outlet>();
		for (String packageName : packageNamesToPackageFolders.keySet()) {
			IFolder folder = packageNamesToPackageFolders.get(packageName);
			outlets.add(createOutlet(packageName, folder));
		}
		// add default outlet: FN_SR C
		outlets.add(createOutlet(null,
				getDescriptor().getProject().getFolder(FN_SRC)));
		return outlets;
	}

	private Outlet createOutlet(String name, IFolder folder) {
		Outlet o = new Outlet(folder.getLocation().toOSString());
		o.setName(getNameForPackageName(name));
		o.addPostprocessor(new JavaBeautifier());
		o.addVetoStrategy(new NoChangesVetoStrategy());
		o.setFileEncoding(FILE_ENCODING);
		System.out.println("Outlet created with name: " + o.getName());
		return o;
	}

	private String getNameForPackageName(String pName) {
		if (pName == null)
			return null;
		String ret = "";
		if (pName.contains(".")) {
			int lastDot = pName.lastIndexOf(".");
			ret = pName.substring(lastDot + 1).toUpperCase();
		}
		return ret;
	}

	/**
	 * Creates a simple General Eclipse project with a ejbModule folder inside.
	 * 
	 * @param monitor
	 * @throws CoreException
	 * @throws ProjectNameNotSettedException
	 */
	private void generateProject(IProgressMonitor monitor)
			throws CoreException, ProjectNameNotSettedException {
		String projectName = getDescriptor().getEjbProjectName();
		if (projectName == null || projectName.isEmpty()) {
			throw new ProjectNameNotSettedException();
		}
		IWorkspace ws = ResourcesPlugin.getWorkspace();
		IProject project = ws.getRoot().getProject(projectName);
		final IProjectDescription description = ws
				.newProjectDescription(project.getName());
		if (!project.exists()) {
			// create the new project
			project.create(description, new SubProgressMonitor(monitor, 10));
		}
		if (monitor.isCanceled())
			throw new OperationCanceledException();
		if (!project.isOpen()) {
			// open the project
			project.open(IResource.BACKGROUND_REFRESH, new SubProgressMonitor(
					monitor, 10));
		}
		// project generated
		getDescriptor().setProject(project);
	}

	/**
	 * Creates a folder in the given container.
	 * 
	 * @param container
	 * @param folderName
	 * @param monitor
	 * @throws CoreException
	 */
	private IFolder createFolder(IContainer container, String folderName,
			IProgressMonitor monitor) throws CoreException {
		if (folderName.length() > 0) {
			IFolder folder = container.getFolder(new Path(folderName));
			if (!folder.exists()) {
				folder.create(IResource.BACKGROUND_REFRESH, true, monitor);
			}
			return folder;
		}
		return null;
	}

	public SeamGenerationDescriptor getDescriptor() {
		return descriptor;
	}
}
